-- #########################
-- Hooks
-- #########################

GM.Hooks = {}
GM.EntHooks = {}

-- Enums.
PRIORITY_HIGH = 1
PRIORITY_MED = 2
PRIORITY_LOW = 3
PRIORITY_DEFAULT = PRIORITY_LOW

-- Add hooks within gamemode.
-- Usage: self:AddHook( gamemode function to hook, gamemode function hooked, priority 1-3 )
-- Example: self:AddHook( "Think", "RoundThink" )
-- Or: self:AddHook( "Move", "SuperjumpMove", 1 )
function GM:AddHook( hookName, funcName, priority )
	if type(priority) == "function" then
		self.Hooks[ hookName ] = self.Hooks[ hookName ] or {}
		self.Hooks[ hookName ][ funcName ] = priority
	else
		self.Hooks[ hookName ] = self.Hooks[ hookName ] or {}
		self.Hooks[ hookName ][ funcName ] = math.Clamp( priority or PRIORITY_DEFAULT, 1, 3 )
	end
	
	hook.Add( hookName, "GMHooks." .. hookName, function( ... ) return ( GM or GAMEMODE ):CallHook( hookName, ... ) end )
end

function GM:RemoveHook( hookName, funcName )
	self.Hooks[ hookName ] = self.Hooks[ hookName ] or {}
	self.Hooks[ hookName ][ funcName ] = nil
end

-- Add hooks for entities.
-- Usage: GM:AddEntityHook( gamemode function, entity class, entity function )
-- Example: GM:AddEntityHook(  "FlagCapped", "gmf_flag", "FlagCapped" )
function GM:AddEntityHook( hookName, class, funcName )
	self.EntHooks[ hookName ] = self.EntHooks[ hookName ] or {}
	self.EntHooks[ hookName ][ class ] = funcName
	
	hook.Add( hookName, "GMHooks." .. hookName, function( ... ) return ( GM or GAMEMODE ):CallHook( hookName, ... ) end )
end
function GM:RemoveEntityHook( hookName, class, funcName )
	self.EntHooks[ hookName ] = self.EntHooks[ hookName ] or {}
	self.EntHooks[ hookName ][ class ] = nil
end

-- Calls the hooks.
-- Usage: GM:CallHook( gamemode function, arguments )
-- Example: GM:CallHook( "PlayerSpawn", pPlayer )
function GM:CallHook( hookName, ... )
	-- Gamemode functions.
	for priority = 1, 3 do
		for funcName, funcPri in pairs( self.Hooks[ hookName ] or {} ) do
			if ( funcPri == priority ) then
				local func = self[ funcName ]
				if ( func ) then
					local ok, retval = pcall( func, self, ... )
					if ( ok ) then
						if ( retval != nil ) then return retval end
					else
						ErrorNoHalt( retval )
					end
				end
			end
		end
	end
	
	-- Plain functions.
	for unique, func in pairs( self.Hooks[ hookName ] or {} ) do
		if type(func) == "function" then
			local ok, retval = pcall( func, self, ... )
			if ( ok ) then
				if ( retval != nil ) then return retval end
			else
				ErrorNoHalt( retval )
			end
		end
	end
	
	-- Entity functions.
	for class, funcName in pairs( self.EntHooks[ hookName ] or {} ) do
		for _, ent in pairs( ents.FindByClass( class ) ) do
			if ( ent[ funcName ] ) then
				local ok, retval = pcall( ent[ funcName ], ent, ... )
				if ( ok ) then
					if ( retval != nil ) then return retval end
				else
					ErrorNoHalt( retval )
				end
			end
		end
	end
end

-- #########################
-- Console commands
-- #########################

GM.ConsoleCommands = {}

local function ConCommand( ply, command, args )
	printd ("concommand received: ", ply, command)
	
	local func = GAMEMODE.ConsoleCommands[ command ]
	if ( !func ) then return end -- It's been removed.
	
	-- Call the function if it exists.
	if ( GAMEMODE[ func ] ) then
		local ok, error = pcall( GAMEMODE[ func ], GAMEMODE, ply, command, args )
		if ( !ok ) then Error( error ) end
	end
end

-- Hooks a console command to a gamemode function.
-- Usage: GM:AddConCommand( console command, gamemode function )
-- Example: GM:AddConCommand( "+undo", "ILikeToUndo" )
function GM:AddConCommand( command, func )
	self.ConsoleCommands[ command ] = func
	concommand.Add( command, ConCommand )
end
function GM:RemoveConCommand( command )
	self.ConsoleCommands[ command ] = nil
end

-- #########################
-- Folder structure
-- #########################

if ( SERVER ) then
	AddCSLuaFile()
end

-- Path passed must have a trailing slash
local function includeFolder(path, ...)
	local funcs = {...}

	local files, folders = file.Find(path .. "*.lua", "LUA")
	for _, filename in pairs(files) do
		for _, f in pairs(funcs) do
			f(path .. filename)
		end
	end
end

if SERVER then
	includeFolder(GM.FolderName .. "/gamemode/server/", include)
	includeFolder(GM.FolderName .. "/gamemode/client/", AddCSLuaFile)
	includeFolder(GM.FolderName .. "/gamemode/shared/", include, AddCSLuaFile)
end
if CLIENT then
	includeFolder(GM.FolderName .. "/gamemode/client/", include)
	includeFolder(GM.FolderName .. "/gamemode/shared/", include)
end